package com.atguigu.gmall.common.rabbit.config;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.rabbit.model.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Author: 小兰
 * @Date: 2023/5/8 20:21
 */
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ReturnCallback, RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 为IOC容器中RabbitTemplate模板对象设置生产者两个回调方法
     */
    /**
     * 应用启动后触发一次
     */
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);

    }
    /**
     *交换机确认:当消息发送到交换机交换机将结果(成功或者失败)异步发送给生产者
     * @param correlationData 回调相关性数据
     * @param b   应答结果 true:成功   false:失败
     * @param s    发送消息到交互机失败原因;当失败才有值
     */

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        log.info("[生产者确认之交换机确认],相关数据:{}, 回调结果:{}, 失败原因:{}", correlationData, b, s);
        if (b){
            log.info("消息发送到交互机成功!");
        }else {
            log.error("消息发送到交互机失败,失败原因:{}", s);
            //TODO 进行消息重发
            GmallCorrelationData gmallCorrelationData=(GmallCorrelationData) correlationData;
            this.retrySendMsg(gmallCorrelationData);
        }
    }

    /**
     * 消息的重发
     * @param gmallCorrelationData
     */
    private void retrySendMsg(GmallCorrelationData gmallCorrelationData) {
        //获取相关数据;判断是否满足重试条件,,重发消息
        String uuId = gmallCorrelationData.getId();
        String gmallCorrelationDataStr = (String) redisTemplate.opsForValue().get(uuId);
        gmallCorrelationData =  JSON.parseObject(gmallCorrelationDataStr, GmallCorrelationData.class);
        //方式一:解决延迟插件必然导致队列确认失败方法
        if (gmallCorrelationData.isDelay()){
            return;
        }
        int retry=gmallCorrelationData.getRetryCount();
        if (retry>=3){
            log.error("重试达到上限,你龙马!,将失败消息写入死信队列中消息;人工处理");
            return;
        }
        //调用模板对象将消息重新发一次
        System.out.println("进行消息重发");
        gmallCorrelationData.setRetryCount(gmallCorrelationData.getRetryCount()+1);
        redisTemplate.opsForValue().set(uuId, JSON.toJSONString(gmallCorrelationData));
        //方式二:延迟消息重发判断
        if (gmallCorrelationData.isDelay()){
            //延迟消息重发-设置消息延迟时间
            GmallCorrelationData finalGmallCorrelationData = gmallCorrelationData;
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(), gmallCorrelationData.getMessage(),
                    (message) ->{
                //设置重发消息延迟时间
                message.getMessageProperties().setDelay(finalGmallCorrelationData.getDelayTime()*1000);
                return  message;
                    }
                    ,gmallCorrelationData);

        }else {
            //普通消息重发-立即发送
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(), gmallCorrelationData.getMessage(),gmallCorrelationData);
        }

    }

    /**
     * 交换机确认之队列确认:交换机路由消息到队列失败才会将结果发送给生产者
     * @param message  返回消息对象
     * @param i  应答码
     * @param s  错误原因
     * @param s1   交换机名称
     * @param s2   路由键
     */

    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        log.error("队列确认失败,消息路由queue失败，应答码={}，原因={}，交换机={}，路由键={}，消息={}",
                i, s, s1, s2, message.toString());
        //TODO 进行消息重发
        //1.先从消息对象中消息属性对象中获取头(相关数据唯一标识-redis中相关数据key)
        String uuId = message.getMessageProperties().getHeader("spring_returned_message_correlation");

        //2.从redis中获取相关数据
        String str = (String)redisTemplate.opsForValue().get(uuId);
        if (StringUtils.isNotBlank(str)){
            GmallCorrelationData gmallCorrelationData = JSON.parseObject(str, GmallCorrelationData.class);
            //3.调用重发消息方法即可-普通消息
            this.retrySendMsg(gmallCorrelationData);
        }
    }
}
